/*
 *----------------------------------------------------------------------------
 *	LSI Corporation
 *	1621 Barber Lane
 *	Milpitas, California 95035
 *----------------------------------------------------------------------------
 * Copyright © 2004-2006, LSI Corporation All Rights Reserved.
 * LSI's source code is an unpublished work and the use of copyright
 * notice does not imply otherwise. This source code contains confidential,
 * trade secret material of LSI Corporation. Any attempt or
 * participation in deciphering, decoding, reverse engineering or
 * in any way altering the source code is strictly prohibited, unless the
 * prior written consent of LSI Corporation.
 *
 * Description	:
 * This file contains the Linux OS implementation of the OSS routines.
 *
 *---------------------------------------------------------------------------- */
#include <linux/types.h>
#include <linux/spinlock.h>

#include "../../cmn_defs.h"
#include "lin_oss.h"
#include "../oss_iface.h"
#include "../../osl/lin/linux_osl.h"

extern void osl_timeout(unsigned long);
pvt_data_t * lin_get_linux_adp(void *data);
extern spinlock_t scu_lock;
/**
 *@brief
 * Operating System Layer Spin lock IRQ restore
 * This function is used to lock the driver for critical operations and it 
 * is used for SMP support.
 * 
 *@param 	os_context	: Pointer to OS specific information structure
 *@param 	flags		: Spin lock information flag
 *@return	None
 */
void_t oss_spin_lock_irqsave(pvt_data_t* os_context, unsigned long *flags)
{
	linux_adp_t * lxadp = (linux_adp_t*) lin_get_linux_adp(os_context);

	spin_lock_irqsave(lxadp->host_lock,*flags);
}

void_t oss_scu_spin_lock_irqsave(pvt_data_t* os_context, unsigned long *flags)
{
    spin_lock_irqsave(&scu_lock,*flags);
}

void_t oss_scu_spin_unlock_irqrestore(pvt_data_t* os_context, unsigned long flags)
{
    spin_unlock_irqrestore(&scu_lock,flags);
}
/**
 *@brief
 * Operating System Layer Spin unlock IRQ restore
 * This function is used to unlock the driver for critical operations and it 
 * is used for SMP support.
 * 
 * In windows miniport driver this function is not required as the 
 * driver is single threaded
 *
 *@param 	os_context	: Pointer to OS specific information structure
 *@param 	flags		: Spin lock information flag captured while locking
 *@return	None
 */
void_t oss_spin_unlock_irqrestore(pvt_data_t* os_context, unsigned long flags)
{
	linux_adp_t * lxadp = (linux_adp_t*) lin_get_linux_adp(os_context);

	spin_unlock_irqrestore(lxadp->host_lock,flags);
}

boolean_t oss_check_if_spinlock_acquired(pvt_data_t *os_context) 
{

	linux_adp_t * lxadp = (linux_adp_t*) lin_get_linux_adp(os_context);
	if (!spin_is_locked(lxadp->host_lock))
		return LSI_FALSE;
	else
		return LSI_TRUE;
}
	

/**
 *@brief
 * Operating System Layer Spin lock
 * This function is used to lock the driver for critical operations and it 
 * is used for SMP support.
 * 
 * In windows miniport driver this function is not required as the 
 * driver is single threaded
 *
 *@param 	os_context	: Pointer to OS specific information structure
 *@return	None
 */
void_t oss_spin_lock(pvt_data_t *os_context)
{
	linux_adp_t * lxadp = (linux_adp_t*) lin_get_linux_adp(os_context);

	spin_lock(lxadp->host_lock);
}

/**
 *@brief
 * Operating System Layer Spin unlock
 * This function is used to unlock the driver for critical operations and it 
 * is used for SMP support.
 * 
 * In windows miniport driver this function is not required as the 
 * driver is single threaded
 *
 *@param 	os_context	: Pointer to OS specific information structure
 *@return	None
 */
void_t oss_spin_unlock(pvt_data_t *os_context)
{
	linux_adp_t * lxadp = (linux_adp_t*) lin_get_linux_adp(os_context);

	spin_unlock(lxadp->host_lock);
}

void_t oss_clear_aen_flag ( void_t ) 
{
	megasr_poll_wait_aen = 0;
}

void_t oss_memset(uint8_t *buffer, uint32_t length, uint8_t fill_char)
{
	memset(buffer, fill_char, length);
}

void_t oss_memcpy(uint8_t *src, uint8_t *dst, uint32_t count)
{
	memcpy(dst, src, count);
}

uint8_t *oss_claim_resource(pvt_data_t *os_context, io_resource_t *io_res)
{
	uint32_t	length	= io_res->length;
	unsigned long		baddr	= (unsigned long) io_res->paddr;

	if(io_res->io_type == RES_TYPE_MEM) {
		
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
		if(!request_mem_region(baddr, length, LSIRAID_DRIVER_SIGNATURE)) {
		con_log(CL_ERR, ("megaswr[osl]: mem region busy\n"));
		return NULL;
		}
#else
		if (check_mem_region(baddr, length)) {
		con_log(CL_ERR, ("megaswr[osl]: mem region busy\n"));
		return NULL;
		}
		if(!request_mem_region(baddr, length, LSIRAID_DRIVER_SIGNATURE)) {
		con_log(CL_ERR, ("megaswr[osl]: mem region busy\n"));
		return NULL;
		}
#endif
		io_res->vaddr = (uint8_t*) ioremap_nocache(baddr, length);

		return io_res->vaddr;

	} else {
		if(!request_region(baddr, length, LSIRAID_DRIVER_SIGNATURE))
			return NULL;

		io_res->vaddr = (uint8_t*) baddr;
		return io_res->vaddr;
	}
}

uint32_t oss_free_resource(pvt_data_t *os_context, io_resource_t *io_resource)
{
	iounmap(io_resource->vaddr);

	if(io_resource->length)
		release_mem_region((unsigned long)io_resource->paddr, io_resource->length);

	return 0;
}

void_t oss_stall_execution(pvt_data_t *os_context, uint32_t micro_secs)
{
	for(; micro_secs; micro_secs--) {
		udelay(1);
		rmb();
	}
}

/* Blocking sleep for windows and it is same as oss_stall_execution  */
void_t oss_stall_execution_msleep(pvt_data_t *os_context, uint32_t micro_secs)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,10)
    msleep(micro_secs/1000);
#else //msleep not avilable and also the problem of stack dump does not appear.
    oss_stall_execution(os_context, micro_secs);
#endif
}

/* Interruptible  sleep for linux and it is same as oss_stall_execution  */
void_t oss_stall_execution_msleep_interruptible(pvt_data_t *os_context, uint32_t micro_secs)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,10)
    msleep_interruptible(micro_secs/1000);
#else //msleep not avilable and also the problem of stack dump does not appear.
    oss_stall_execution(os_context, micro_secs);
#endif
}

uint16_t oss_read_port_ushort(pvt_data_t *os_context, uint16_t *addr)
{
	return inw((unsigned long) addr);
}

uint8_t oss_read_port_uchar(pvt_data_t *os_context, uint8_t *addr)
{
	return inb((unsigned long) addr);
}

uint32_t oss_read_port_ulong(pvt_data_t *os_context, uint32_t *addr)
{
	return inl((unsigned long) addr);
}

uint32_t oss_read_register_ulong(pvt_data_t *os_context, uint32_t *addr)
{
	return readl(addr);
}

void_t oss_read_port_buffer_ushort(pvt_data_t *os_context, uint16_t *addr,
		uint16_t *buffer, uint32_t count)
{
	insw((unsigned long) addr, buffer, count);
}

void_t oss_write_register_ulong(pvt_data_t *os_context, uint32_t *addr,
		uint32_t value)
{
	writel(value, addr);
}

void_t oss_write_port_ulong(pvt_data_t *os_context, uint32_t *addr, uint32_t
		value)
{
	outl(value, (unsigned long) addr);
}

void_t oss_write_port_uchar(pvt_data_t *os_context, uint8_t *addr, uint8_t
		value)
{
	outb(value, (unsigned long) addr);
}

void_t oss_breakpoint(void_t)
{
	con_log(CL_ERR, ("megaswr[osl]: breakpoint called\n"));
	/**@todo manoj implement linux driver panic function */
}

void_t oss_rescan(pvt_data_t *os_context, uint8_t bus)
{
	linux_adp_t *lxadp	= (linux_adp_t*) lin_get_linux_adp(os_context);

	con_log(CL_ERR, ("megaswr[osl]: oss rescan set \n"));

	lxadp->is_rescan = 1; //set to trigger rescan later
	
	
}

/*
 * This routine returns a new handle to a timer entry.
 */
handle_t oss_create_timer(pvt_data_t *os_cxt)
{
	uint8_t				i;
	linux_adp_t			*lxadp;
	timer_object_t		*to = NULL;
	struct timer_list	*tp;

	if (!os_cxt) {
		con_log(CL_ERR, ("megaswr[osl]:  err Invalid parameter\n"));
		return INVALID_HANDLE;
	}

	lxadp	= (linux_adp_t*) lin_get_linux_adp(os_cxt);

	if (lxadp->timer_count > MAX_TIMERS) {
		con_log(CL_ERR, ("megaswr[osl]:  err Too many timers\n"));
		return INVALID_HANDLE;
	}

	for(i = 0; i < MAX_TIMERS; i++) {
		if(!lxadp->timer_objects[i].used_flag) {
			to		= &lxadp->timer_objects[i];
			lxadp->timer_objects[i].used_flag = 1;
			break;
		}
	}

	if (!to) {
		con_log(CL_ERR, ("megaswr[osl]:  err could not find a timer object\n"));
		return INVALID_HANDLE;
	}

	lxadp->timer_count++;
	to->active_flag	= 0;
	to->seconds	= 0;
	to->timer_fn	= NULL;
	to->context1	= NULL;
	to->context2	= NULL;

	tp		= &to->timer;
	tp->function	= osl_timeout;
	tp->data	= (unsigned long) to;
	tp->expires	= 0;

	init_timer(tp);
	con_log(CL_INFO, ("megaswr[OS] Timer create handle = %p  count = %d\n",to,lxadp->timer_count));
	return ((handle_t)to);
}

/*
* Description	:	Performs any necessary action needed to restart our main timer thread with the OS.
*
* Parameters	:
* @param1 pv	: os_ext - this should be the actual OS device extension, not our internal context data structure
*
* Return	:
*	none
*/
void_t oss_resume_timer(pvt_data_t *os_ext)
{
	/* won't be used for Linux */
	return;
}

void_t oss_megasr_timer(pvt_data_t *os_ext)
{
	/* won't be used for Linux */
	return;
}

void_t oss_megasr_poll_timer(pvt_data_t *os_ext)
{
	/* won't be used for Linux */
	return;
}

/*
 * This routine is called to schedule the timer thread.
 */
uint32_t oss_schedule_timer(pvt_data_t *os_cxt, handle_t handle, uint32_t
		seconds, timer_fn_t fn, void_t *context1, void_t *context2)
{
	timer_object_t*		to;
	struct timer_list*	tp;

	if(handle == INVALID_HANDLE)
       return LSI_FALSE;
	
	if((!os_cxt) || (!handle) || (seconds <= 0) || (!fn)){
		con_log(CL_DEBUG, ("megaswr[osl]: Schedule Timer: Invalid parameter\n"));
		return LSI_FALSE;
	}

	to			= (timer_object_t*) handle;
	con_log(CL_INFO, ("megaswr[OS] Timer schedule handle = %p \n",to));
	to->active_flag	= 1;
	to->seconds	= seconds;
	to->timer_fn= fn;
	to->context1= context1;
	to->context2= context2;

	tp			= &to->timer;
	tp->function= osl_timeout;
	tp->data	= (unsigned long) to;
	tp->expires	= jiffies + seconds * HZ;

    /* We cant reuse the timer handle in Linux using add_timer.*/ 
    if(!to->used_flag)
		add_timer(tp);
	else
		mod_timer(tp,tp->expires);
	
	return LSI_TRUE;
}

/*
 * This routine is called to destroy timer.
 */
void_t oss_destroy_timer(pvt_data_t *os, handle_t handle)
{
	linux_adp_t		*lxadp = (linux_adp_t*) lin_get_linux_adp(os);
	
	con_log(CL_INFO, ("megaswr[OS] Timer destroy handle = %p  count = %d\n",((timer_object_t *)handle),lxadp->timer_count));	
	if (handle == INVALID_HANDLE) {
		con_log(CL_ERR, ("megaswr[osl]:  err distroy timer with invalid handle\n"));
		return;
	}
	
	if (((timer_object_t*) handle)->active_flag) {
		del_timer(&(((timer_object_t*) handle)->timer));

		((timer_object_t*) handle)->active_flag	= 0;
	}

	lxadp->timer_count --;
	((timer_object_t*) handle)->used_flag = 0;
}


uint32_t oss_schedule_millisecond_timer(pvt_data_t *os_cxt, handle_t handle, uint32_t
		milliseconds, timer_fn_t fn, void_t *context1, void_t *context2)
{
    struct timer_list*      tp;
    timer_object_t		*to = (timer_object_t *)handle;
	
    
    if(to == NULL)
      return -1;

    if( (!to) || (milliseconds <= 0) )
    {
       con_log(CL_INFO, (" [SCU]: Schedule Timer: Invalid parameter \n"));
       return -1;
    }
    //if(to->resreved1) {
    to->active_flag = 1;
    to->seconds     = milliseconds;
    to->timer_fn= fn;
    to->context1= context1;
    to->context2= context2;

    tp  = &to->timer;
    tp->data        = (unsigned long) to;
    tp->function= osl_timeout;
    tp->expires     = jiffies + msecs_to_jiffies(milliseconds); //TODO need to check for differnt linux kernel version
/*if(!to->used_flag)
	add_timer(tp);
else*/
    mod_timer(tp,tp->expires);
    //}
    return LSI_TRUE;
}


/**
 * XOR buffer.
 *
 *@param dest: Destination address.
 *@param buf1: first buffer.
 *@param buf2: second buffer.
 *@param buf3: third buffer and is optional.  NULL if not valid.
 *@param xor_len: Number of DWORDS to xor.
 *@return	: None
 */
void_t oss_xor_buffer(uint32_t *dest, uint32_t *buf1, uint32_t *buf2, uint32_t
		*buf3, uint32_t xor_len)
{
	uint32_t ind;

	if ( !buf3 ) {
		for(ind=0;ind<xor_len;ind++) {
			dest[ind] = buf1[ind] ^ buf2[ind];
		}
	} else {
		for(ind=0;ind<xor_len;ind++) {
			dest[ind] = buf1[ind] ^ buf2[ind] ^ buf3[ind];
		}
	}
	return;
}

/**
 *@brief
 * Returns the OS time in seconds since January 1, 2000.
 *@param	os_context: pointer to the os_context structure.
 *@param	os_time: pointer to the variable that will store the value.
 *@return LSI_TRUE if operation was successful, LSI_FALSE otherwise.
 */
boolean_t oss_get_os_time(uint32_t *os_time)
{
	/* CMOS time counted from 1970, So reducing 30 years */
#ifdef __VMKERNEL_MODULE__
	struct timeval tv;
	
	memset(&tv, 0, sizeof(tv));
	do_gettimeofday(&tv);
	
	*os_time = (tv.tv_sec - ((30*365+30/4)*24*60*60));

#else
	*os_time = (LSI_RAID_CURRENT_TIME_IN_SEC - ((30*365+30/4)*24*60*60));
#endif
	return LSI_TRUE;
}


boolean_t oss_get_os_time_for_pr(uint32_t *os_time)
{
	/* CMOS time counted from 1970, So reducing 30 years */
#ifdef __VMKERNEL_MODULE__
	struct timeval tv;
	
	memset(&tv, 0, sizeof(tv));
	do_gettimeofday(&tv);
	
	*os_time = (tv.tv_sec - ((30*365+30/4)*24*60*60));

#else
	*os_time = (LSI_RAID_CURRENT_TIME_IN_SEC - ((30*365+30/4)*24*60*60));
#endif
	return LSI_TRUE;
}


/**
 *@brief
 * Compares count bytes of memory starting at buf1 and buf2 and find if equal
 * or which one is first in lexical order.
 *
 *       returns < 0 if buf1 < buf2
 *       returns  0  if buf1 == buf2
 *       returns > 0 if buf1 > buf2
 */
int32_t oss_memcmp(uint8_t *buf1, uint8_t *buf2, uint32_t count)
{
	return (memcmp(buf1, buf2, count));
}

/**
*@brief
* Moves memory
*
*@param	dest: pointer to the destination buffer to move memory to.
*@param	src: pointer to the source buffer to memory from.
*@param	size: size of the buffers.
*@return none.
*/
void_t oss_memmove(uint8_t *dest, uint8_t *src, uint32_t size)
{
	memmove(dest, src, size);
}

/**
*@brief
* In supported operating systems, sets the specified device's queue depth in the OS.
*
*@param	os_context: pointer to the os_context structure.
*@param	bus: bus number of the device.
*@param	target: target number of the device.
*@param	lun: lun number of the device.
*@param	depth: number of I/O this device can handle.
*@return LSI_TRUE: successful. If not supported by OS, LSI_TRUE will be returned.
*		 LSI_FALSE: failed by OS.
*/
boolean_t oss_set_device_queue_depth(pvt_data_t *os_context, uint8_t bus, uint8_t target, uint8_t lun, uint32_t depth)
{
	return LSI_TRUE;
}

#ifndef COMPILE_64BIT
/**
 * 64-bit division to get remainder on a 32-bit platform
 *
 * @param dividend	: Dividend
 * @param divisor	: Divisor
 *
 * @return remainder
 **/
uint32_t megasr_mod64(uint64_t dividend, uint32_t divisor)
{
	uint32_t		remainder;
	unsigned long	eax_saved;
	unsigned long	ebx_saved;
	unsigned long	edx_saved;

	// save used registers
	asm("" : "=a" (eax_saved), "=d" (edx_saved), "=b" (ebx_saved):);

	asm("divl %1" : "=d" (remainder) : "b" (divisor), "A" (dividend));

	// restore used registers
	asm("" : : "a" (eax_saved), "b" (ebx_saved), "d" (edx_saved));

	return remainder;
}

/**
 * 64-bit division on a 32-bit platform
 *
 * @param dividend	: Dividend
 * @param divisor	: Divisor
 *
 * @return quotient
 **/
uint64_t megasr_div64(uint64_t dividend, uint32_t divisor)
{

    /* When quotient becomes greater than 32 bit overflow occurs with this logic */

	/*
	uint32_t		quotient;
	unsigned long	eax_saved;
	unsigned long	ebx_saved;
	unsigned long	edx_saved;

	// save used registers
	asm("" : "=a" (eax_saved), "=d" (edx_saved), "=b" (ebx_saved):);

	asm("divl %1" : "=a" (quotient) : "b" (divisor), "A" (dividend));

	// restore used registers
	asm("" : : "a" (eax_saved), "b" (ebx_saved), "d" (edx_saved));

	return quotient; */
	

	uint32_t low, high, rem;

        low = dividend & 0xffffffff;

        high = dividend >> 32;

        if (high) {

                u_int32_t high1 = high % divisor;

                high /= divisor;

                asm("divl %2":"=a" (low), "=d" (rem):"rm" (divisor), "a" (low), "d" (high1));

                dividend = (u_int64_t)high << 32 | low;

        } else {

                dividend = low / divisor;

                rem = low % divisor;

        }

        return dividend;
}
#endif
/********************************scu related function calls ************************/
void_t *oss_new_claim_resource(pvt_data_t *os_context, io_resource_t_64 *io_res,
int bar_no)
{
	linux_adp_t * lxadp = (linux_adp_t*) lin_get_linux_adp(os_context);
	struct pci_dev *pdev = (struct pci_dev *)lxadp->pdev;
	uint64_t baddr;
	uint32_t length;

	if (pci_request_region (pdev, bar_no * 2, LSIRAID_DRIVER_SIGNATURE)) {
		con_log(CL_ERR, ("megaswr[oss]: pci_request_region failed for bar = %d\n", bar_no));
		return (uint8_t *)NULL;
	}

	io_res->paddr = (uint64_t)(pci_resource_start (pdev, bar_no * 2));
	io_res->length = pci_resource_len (pdev, bar_no * 2);
	/* ioremap cache expects parameters in this form */
	baddr = (uint64_t)io_res->paddr;
	length = io_res->length;
	io_res->vaddr = (void_t *) ioremap_nocache(baddr,length);
	if (io_res->vaddr == NULL) {
		con_log(CL_ERR, ("megaswr[oss]: "
		    "io_remap failed for bar = %d\n", bar_no));
		pci_release_region( pdev, bar_no * 2);
		io_res->vaddr = NULL;
		io_res->paddr = 0;
		io_res->length = 0;
		return io_res->vaddr; // NULL Address
	}
	con_log(CL_INFO, ("megaswr[oss]: pci_bar_region %d addr:%lx\n", bar_no,io_res->paddr));
	return io_res->vaddr;
}

uint32_t oss_new_free_resource(pvt_data_t *os_context, io_resource_t_64 *io_resource, int bar_no)
{
	linux_adp_t * lxadp = (linux_adp_t*) lin_get_linux_adp(os_context);
	struct pci_dev *pdev = (struct pci_dev *)lxadp->pdev;

	con_log(CL_ERR, ("oss_new_free_resource:........... bar_no: %d, io_resource :%x,  io_resourc->length:%x \n", bar_no, io_resource, io_resource->length));
	if(io_resource->length)
	{
		con_log(CL_ERR, ("oss_new_free_resource: length: %lx\n", io_resource->length));
		pci_release_region(pdev, bar_no * 2);
	}

	if(io_resource->vaddr) 
	{
		con_log(CL_ERR, ("oss_new_free_resource: vaddr: %lx\n", io_resource->vaddr));
		iounmap(io_resource->vaddr);
	}
	io_resource->vaddr = NULL;
	io_resource->paddr = 0;
	io_resource->length = 0;
	
	//if(bar_no  == (SCU_MAX_BAR_REG_TO_MAP - 1))
	#if 0
	if(bar_no  == 0)
	{
		con_log(CL_ERR, ("oss_new_free_resource: bar_no: %d\n", bar_no));
		pci_disable_device(pdev);
	}
	#endif
	return LSI_TRUE;
}

uint64_t oss_get_phys_addr(pvt_data_t *os_context, void * virtual_addr)
{
#ifdef LSI_LINUX_IOMMU_VIRT_PHYS
	return lsi_mem_virt_phys(os_context, (uint64_t)virtual_addr, LSI_VIRT_TO_PHYS);
#else

#ifdef CONFIG_XEN
	return (uint64_t)(virt_to_bus(virtual_addr));
#endif

	return (uint64_t)(virt_to_phys(virtual_addr));
#endif /* LSI_LINUX_IOMMU_VIRT_PHYS */

}

void * oss_get_virt_addr(pvt_data_t *os_context, uint64_t physical_address)
{
#ifdef LSI_LINUX_IOMMU_VIRT_PHYS
	return (void *)lsi_mem_virt_phys(os_context, physical_address, LSI_PHYS_TO_VIRT);
#else

#ifdef CONFIG_XEN
	return bus_to_virt(physical_address);
#endif

	return (phys_to_virt(physical_address));
#endif /* LSI_LINUX_IOMMU_VIRT_PHYS */

}


boolean_t oss_alloc_mem_for_scu(pvt_data_t *os_context, uint32_t size_in_bytes, scu_addr_t *addr)
{
        linux_adp_t         *lxadp      = (linux_adp_t *)lin_get_linux_adp(os_context);
        void                *rc = NULL;
        dma_addr_t   dma_hdl;

        /* Validate data passed in */
        if (!size_in_bytes) {
                con_log(CL_ERR, ("megaswr[oss]:  err allocate_mem called with zero size to allocate!\n"));
                return LSI_FALSE;
        }
        if (!addr) {
                con_log(CL_ERR, ("megaswr[oss]:  err allocate_mem called without scu_addr_t\n"));
                return LSI_FALSE;
        }

        con_log(CL_DEBUG, ("megasr[oss]:%s  \n",__FUNCTION__));

        rc = pci_alloc_consistent(lxadp->pdev, size_in_bytes, &dma_hdl);
        if (rc) {
            addr->vaddr = (uint8_t *)rc;
            addr->paddr = (uint8_t *)(unsigned long)dma_hdl;
            oss_memset(addr->vaddr, size_in_bytes, 0);
            return LSI_TRUE;
        }
        return LSI_FALSE;
}

/* vim: set ts=4 sw=4 tw=78 wrap ai si: */
